package com.yanyeori.framework.security.filter;

import com.yanyeori.framework.core.context.UserContext;
import com.yanyeori.framework.core.model.WebResponse;
import com.yanyeori.framework.core.util.DigestUtil;
import com.yanyeori.framework.core.util.ServletUtil;
import com.yanyeori.framework.core.util.StringUtil;
import com.yanyeori.framework.security.bo.UserDetail;
import com.yanyeori.framework.security.cache.PermissionsCache;
import com.yanyeori.framework.security.config.ConfigFactory;
import com.yanyeori.framework.security.configprop.SignProps;
import com.yanyeori.framework.security.constant.SecurityConst;
import com.yanyeori.framework.security.service.PermissionsService;
import com.yanyeori.framework.security.service.TokenLoginService;
import com.yanyeori.framework.security.util.SecurityUtil;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.Base64Utils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 请求认证逻辑
 *
 * @author chenkuan 2020/11/20
 */
public class AuthenticationFilter extends OncePerRequestFilter {

    private final TokenLoginService tokenLoginService;
    private final PermissionsService permissionsService;

    public AuthenticationFilter(TokenLoginService tokenLoginService, PermissionsService permissionsService) {
        this.tokenLoginService = tokenLoginService;
        this.permissionsService = permissionsService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        //访问的servletPath
        String servletPath = SecurityUtil.getServletPath(request);

        //匿名接口，直接返回
        List<String> annApis = ConfigFactory.getConfig().isDevMode() ? permissionsService.listAnnoInterfaces() : PermissionsCache.ANNO_INTERFACE_LIST;
        if (SecurityUtil.isAnonApi(servletPath, annApis)) {
            handleAnonymous(request);
            chain.doFilter(request, response);
            return;
        }

        //校验Security.Authentication是否已登录
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication != null && authentication.isAuthenticated()) {
            //登陆成功状态，重置为失败，使用token校验
            authentication.setAuthenticated(false);
        }

        //token认证
        WebResponse<String> tokenResponse = tokenAuth(request);
        if (!tokenResponse.isSuccess()) {
            ServletUtil.writeAttributeToRequest(request, SecurityConst.ERR_MSG_KEY, tokenResponse.getMessage());
            //将请求交给过滤器链中的下一个过滤器filter，如果没有filter则交给servlet处理
            chain.doFilter(request, response);
            return;
        }
        String token = tokenResponse.getData();

        //签名验证
        if (ConfigFactory.getConfig().getToken().getSign().isEnableTokenSign()) {
            WebResponse<?> validSignResponse = validSign(request, token);
            if (!validSignResponse.isSuccess()) {
                ServletUtil.writeAttributeToRequest(request, SecurityConst.ERR_MSG_KEY, validSignResponse.getMessage());
                chain.doFilter(request, response);
                return;
            }
        }

        chain.doFilter(request, response);
    }

    /**
     * token认证
     * 支持 Bearer、Basic
     */
    private WebResponse<String> tokenAuth(HttpServletRequest request) {
        WebResponse<String> tokenResponse = SecurityUtil.getToken(request);
        if (!tokenResponse.isSuccess()) return tokenResponse;
        String token = tokenResponse.getData();
        String path = SecurityUtil.getServletPath(request);
        if (token.startsWith(SecurityConst.AUTH_BEARER_PRE)) {
            String bearer = token.replaceFirst(SecurityConst.AUTH_BEARER_PRE, "");
            if (StringUtil.isEmpty(bearer)) {
                return WebResponse.fail("Request url [{}], bearer token format error, token [{}]", path, token);
            }
            //获取用户信息UserDetail
            UserDetail userDetail = tokenLoginService.getUserByToken(bearer);
            if (userDetail != null && userDetail.getUsername() != null) {
                handleLoginSuccess(request, userDetail);
                //token续期
                tokenLoginService.tokenRenewal(bearer, userDetail);
            } else {
                logoutAuthentication();
            }
            return WebResponse.ok(bearer);
        } else if (token.startsWith(SecurityConst.AUTH_BASIC_PRE)) {
            String basic = token.replaceFirst(SecurityConst.AUTH_BASIC_PRE, "");
            if (StringUtil.isEmpty(basic)) {
                return WebResponse.fail("Request url [{}], basic token format error, token [{}]", path, token);
            }
            String decodeBasic = StringUtil.str(Base64Utils.decodeFromString(basic));
            if (StringUtil.isEmpty(decodeBasic) || !decodeBasic.contains(":")) {
                return WebResponse.fail("Request url [{}], basic token format error, token [{}]", path, token);
            }
            String[] arr = decodeBasic.split(":");
            String username = arr[0];
            String password = arr[1];
            WebResponse<Authentication> loginResponse = tokenLoginService.login(request, username, password);
            if (!loginResponse.isSuccess()) {
                return WebResponse.fail(loginResponse.getCode(), loginResponse.getMessage());
            }
            return WebResponse.ok(basic);
        } else {
            return WebResponse.fail("Request url [{}], token format error, unsupported token [{}]", path, token);
        }
    }

    /**
     * 签名验证
     */
    private WebResponse<?> validSign(HttpServletRequest request, String token) {
        SignProps signProps = ConfigFactory.getConfig().getToken().getSign();
        //获取签名时间戳timestamp
        Long timestamp = SecurityUtil.getTimestamp(request);
        if (timestamp == null) {
            return WebResponse.fail("Header not set [{}]", SecurityConst.AUTH_TIMESTAMP);
        }
        //获取token签名
        String sign = SecurityUtil.getSign(request);
        if (StringUtil.isEmpty(sign)) {
            return WebResponse.fail("Header not set [{}]", SecurityConst.AUTH_SIGN);
        }
        //签名验证
        String md5 = DigestUtil.md5(token + timestamp + signProps.getSignSecret());
        if (!md5.equals(sign)) {
            return WebResponse.fail("Sign valid failed");
        }
        //签名过期验证
        if (System.currentTimeMillis() - timestamp > TimeUnit.SECONDS.toMillis(signProps.getSignExpiration())) {
            return WebResponse.fail("Sign timestamp expiration");
        }
        return WebResponse.ok();
    }

    /**
     * 登录成功之后的处理
     *
     * @param request    HttpServletRequest
     * @param userDetail 用户信息
     */
    private void handleLoginSuccess(HttpServletRequest request, UserDetail userDetail) {
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetail, null, userDetail.getAuthorities());
        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        SecurityContextHolder.getContext().setAuthentication(authentication);
        SecurityUtil.saveUserContext(userDetail);
    }

    /**
     * 匿名接口处理
     *
     * @param request HttpServletRequest
     */
    private void handleAnonymous(HttpServletRequest request) {
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority(SecurityConst.ROLE_ANONYMOUS));
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(SecurityConst.ANONYMOUS_PRINCIPAL, null, authorities);
        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        SecurityContextHolder.getContext().setAuthentication(authentication);
        UserContext.clear();
    }

    /**
     * 登录校验失败，isAuthenticated置为false
     */
    private void logoutAuthentication() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication != null && authentication.isAuthenticated()) {
            authentication.setAuthenticated(false);
        }
    }
}
